package com.michael.xx.audiorecoder;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;

import com.michael.xx.audiorecoder.encoder.AudioEncoder;

import java.util.Timer;
import java.util.TimerTask;

public class AudioRecorder {
    private static final String TAG = "AudioRecorder-App";
    private int audioInput = MediaRecorder.AudioSource.MIC;
    private int sampleRateInHz = 441000;
    private int channelCount = AudioFormat.CHANNEL_IN_STEREO;
    private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;

    private int bufferSizeInBytes = 0;
    private AudioRecord audioRecord;
    private Status status = Status.STATUS_NO_READY;

    private Timer timer;

    private TimerTask timerTask;
    private int currentPosition = 0;
    private CallBack callBack;
    private AudioEncoder encoder;

    public AudioRecorder(CallBack callBack, AudioEncoder encoder) {
        this.encoder = encoder;
        status = Status.STATUS_READY;
        this.callBack = callBack;
    }

    public void setAudioInput(int audioInput) {
        this.audioInput = audioInput;
    }

    public void setSampleRateInHz(int sampleRateInHz) {
        this.sampleRateInHz = sampleRateInHz;
    }

    public void setChannelCount(int channelCount) {
        this.channelCount = channelCount;
    }


    public void setEncoder(AudioEncoder encoder) {
        this.encoder = encoder;
    }

    private void startTimer() {
        if (timer == null)
            timer = new Timer();
        if (timerTask != null) {
            timerTask.cancel();
        }
        timerTask = new TimerTask() {
            @Override
            public void run() {
                updateTime();
            }
        };
        timer.schedule(timerTask, 0, 1000);
    }

    private void updateTime() {
        currentPosition++;
        Log.d(TAG, "updateTime() called currentPosition = " + currentPosition);
        if (callBack != null) {
            callBack.onTimeUpdate(currentPosition);
        }
    }

    public void startRecord() {
        Log.d(TAG, "startRecord() called audioSampleRate = " + sampleRateInHz);
        bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz,
                channelCount, audioFormat);
        int sampleSizeInBits = 16;
        if (audioFormat == AudioFormat.ENCODING_PCM_8BIT) {
            sampleSizeInBits = 8;
        }

        if (AudioFormat.CHANNEL_IN_MONO == channelCount) {
            encoder.init(sampleRateInHz, sampleSizeInBits, 1);
        } else {
            encoder.init(sampleRateInHz, sampleSizeInBits, 2);
        }
        Log.d(TAG, "startRecord() called bufferSizeInBytes = " + bufferSizeInBytes);
        audioRecord = new AudioRecord(audioInput, sampleRateInHz, channelCount, audioFormat, bufferSizeInBytes);

        if (status == Status.STATUS_NO_READY) {
            throw new IllegalStateException("not init");
        }
        if (status == Status.STATUS_START) {
            throw new IllegalStateException("is recording ");
        }
        Log.d("AudioRecorder-App", "===startRecord===" + audioRecord.getState());
        new Thread(new Runnable() {
            @Override
            public void run() {
                audioRecord.startRecording();
                recordToFile();
            }
        }).start();

    }

    public void pause() {
        Log.d(TAG, "pause() called");
        status = Status.STATUS_PAUSE;
        audioRecord.stop();
        if (timerTask != null) {
            timerTask.cancel();
        }
    }

    public boolean isRecording() {
        return status == Status.STATUS_START && audioRecord != null && audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING;
    }

    public boolean isInited() {
        return audioRecord != null && audioRecord.getState() == AudioRecord.STATE_INITIALIZED;
    }

    public void resume() {
        if (audioRecord == null) {
            return;
        }
        if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
            audioRecord.startRecording();
            status = Status.STATUS_START;
            startTimer();
        }
    }

    public void stop() {
        Log.d(TAG, "stop() called");
        status = Status.STATUS_STOP;
        currentPosition = 0;
        stopRecorder();
    }

    private void recordToFile() {
        int readSize;
        status = Status.STATUS_START;
        startTimer();
        int pcmBufferSize = bufferSizeInBytes;
        Log.d(TAG, "recordToFile() called pcmBufferSize = " + pcmBufferSize);
        byte[] inputBuffer = new byte[pcmBufferSize];
        byte[] outputBuffer = new byte[pcmBufferSize];
        do {
            if (status == Status.STATUS_START) {
                if (audioRecord != null) {
                    readSize = audioRecord.read(inputBuffer, 0, pcmBufferSize);
                    if (readSize != AudioRecord.ERROR_INVALID_OPERATION && readSize != AudioRecord.ERROR &&
                            readSize != AudioRecord.ERROR_BAD_VALUE &&
                            readSize != AudioRecord.ERROR_DEAD_OBJECT
                    ) {
                        if (encoder != null) {
                            if (readSize > 0) {
//                                Log.d(TAG, "recordToFile() called encoder.encodeBuffer");
                                encoder.encodeBuffer(inputBuffer, 0, readSize, outputBuffer);
                            }
                        }
                    }
                }
            }
            if (status == Status.STATUS_STOP) {
                if (encoder != null) {
                    encoder.close();
                }
                break;
            }
        } while (true);
    }

    public void release() {
        Log.d("AudioRecorder-App", "===release===");
        stopRecorder();
        releaseRecorder();
        status = Status.STATUS_READY;
    }

    private void releaseRecorder() {
        if (audioRecord != null) {
            audioRecord.release();
            audioRecord = null;
        }
    }

    private void stopRecorder() {
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
        if (timerTask != null) {
            timerTask.cancel();
            timerTask = null;
        }
        if (audioRecord != null) {
            try {
                audioRecord.stop();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public int getCurrentPosition() {
        return currentPosition;
    }

    public Status getStatus() {
        return status;
    }

    public String getVoiceFilePath() {
        return encoder == null ? "" : encoder.getDestFile();
    }

    public enum Status {
        STATUS_NO_READY,
        STATUS_READY,
        STATUS_START,
        STATUS_PAUSE,
        STATUS_STOP
    }

    public interface CallBack {
        void onTimeUpdate(int progress);
    }

}